﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace PENet
{
    public class KCPServer<TSession> where TSession : KCPSession, new()
    {
        UdpClient udp;
        IPEndPoint remotePoint;

        private CancellationTokenSource cts;
        private CancellationToken ct;

        public KCPServer()
        {
            cts = new CancellationTokenSource();
            ct = cts.Token;
        }

        private Dictionary<uint, TSession> sessionDic;
        public void Start(string ip, int port)
        {
            sessionDic = new Dictionary<uint, TSession>();

            udp = new UdpClient(new IPEndPoint(IPAddress.Parse(ip), port));
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                udp.Client.IOControl((IOControlCode)(-1744830452), new byte[] { 0, 0, 0, 0 }, null);
            }
            remotePoint = new IPEndPoint(IPAddress.Parse(ip), port);
            KCPTool.ColorLog(KCPLogColor.Green, "Server Start...");
            Task.Run(ServerRecive, ct);
        }

        async void ServerRecive()
        {
            UdpReceiveResult result;
            while (true)
            {
                try
                {
                    if (ct.IsCancellationRequested)
                    {
                        KCPTool.ColorLog(KCPLogColor.Cyan, "SeverRecive Task is Cancelled.");
                        break;
                    }
                    result = await udp.ReceiveAsync();
                    uint sid = BitConverter.ToUInt32(result.Buffer, 0);
                    if (sid == 0)
                    {
                        sid = GenerateUniqueSessionID();
                        byte[] sid_bytes = BitConverter.GetBytes(sid);
                        byte[] conv_bytes = new byte[8];
                        Array.Copy(sid_bytes, 0, conv_bytes, 4, 4);
                        //为什么不重发udp：因为如果client没收到，会重新申请，服务器就不用确保一定到达
                        SendUDPMsg(conv_bytes, result.RemoteEndPoint);
                    }
                    else
                    {
                        TSession session;
                        if (!sessionDic.TryGetValue(sid, out session))
                        {
                            //为什么这里才new加入字典：因为可能会重发sid，如果之前就加入就会new多余session
                            session = new TSession();
                            session.InitSession(sid, SendUDPMsg, result.RemoteEndPoint);
                            session.OnSessionClose = OnServerSessionClose;
                            lock (sessionDic)
                            {
                                sessionDic.Add(sid, session);
                            }
                        }
                        session.ReciveData(result.Buffer);
                    }
                }
                catch (Exception e)
                {
                    KCPTool.Warn($"Server Udp Recive Data Exception:{e}");
                }
            }
        }

        private uint handOutSid = 0;
        public uint GenerateUniqueSessionID()
        {
            lock (sessionDic)
            {
                while (true)
                {
                    ++handOutSid;
                    if (handOutSid == uint.MaxValue)
                    {
                        handOutSid = 1;
                    }
                    if (!sessionDic.ContainsKey(handOutSid))
                    {
                        break;
                    }

                }
            }
            return handOutSid;
        }

        void OnServerSessionClose(uint sid)
        {
            if (sessionDic.ContainsKey(sid))
            {
                lock (sessionDic)
                {
                    sessionDic.Remove(sid);
                    KCPTool.Warn($"Session:{sid} Remove.");
                }
            }
            else
            {
                KCPTool.Error($"Session:{sid} Cannot Find In SessionDic");
            }
        }

        public void CloseServer()
        {
            foreach (var item in sessionDic)
            {
                item.Value.CloseSession();
            }
            sessionDic = null;

            if (udp != null)
            {
                udp.Close();
                udp = null;
                cts.Cancel();
            }
        }

        public void BroadCastMsg(byte[] msg)
        {
            foreach (var item in sessionDic)
            {
                item.Value.SendMsg(msg);
            }
        }

        void SendUDPMsg(byte[] bytes, IPEndPoint remotePoint)
            => udp?.SendAsync(bytes, bytes.Length, remotePoint);
    }
}
